package com.example.mqttdevicesimulator.touchpad;

import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.EditText;
import android.widget.Toast;

import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.alibaba.fastjson.JSON;
import com.example.mqttdevicesimulator.ClientOperation;
import com.example.mqttdevicesimulator.DeviceActivity;
import com.example.mqttdevicesimulator.R;
import com.example.mqttdevicesimulator.UtilConstants;
import com.example.mqttdevicesimulator.VideoChatViewActivity;
import com.example.mqttdevicesimulator.jsonformatinfo.ControlInfo;
import com.example.mqttdevicesimulator.jsonformatinfo.DeviceStatusInfo;
import com.example.mqttdevicesimulator.jsonformatinfo.RegisterInfo;
import com.example.mqttdevicesimulator.jsonformatinfo.RoomBindInfo;

import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttAsyncClient;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

/**
 * Touch pad activity, including a list showing controllable devices, a button for
 * connect, a button for find device, and a button for video call.
 */
public class TouchPadActivity extends AppCompatActivity implements DeviceActivity {

    private String device_info;
    private RegisterInfo registerInfo;
    private boolean isTurnedOn = true;

    private EditText brokerAddressEt;
    private Button findDeviceBtn;

    private TDeviceAdapter tDeviceAdapter;
    private List<TMqttDevice> tDeviceList;

    private MqttAsyncClient client;
    private String broker;
    private String room_id;
    private ClientOperation clientOperation;

    private static final String TAG = "TouchPadActivity";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_touch_pad);

        Intent intent = getIntent();
        device_info = intent.getStringExtra("device_info");
        registerInfo = JSON.parseObject(device_info, RegisterInfo.class);

        initUI();
    }

    @Override
    public void initUI() {
        Button connectBtn = findViewById(R.id.t_connect_btn);
        Button videoBtn = findViewById(R.id.t_video_call_btn);
        findDeviceBtn = findViewById(R.id.t_find_device_btn);
        RecyclerView recyclerView = findViewById(R.id.t_device_recycler_view);
        LinearLayoutManager layoutManager = new LinearLayoutManager(TouchPadActivity.this);

        brokerAddressEt = findViewById(R.id.broker_address_et);

        tDeviceList = new ArrayList<>();
        tDeviceAdapter = new TDeviceAdapter(tDeviceList, TouchPadActivity.this);

        recyclerView.setLayoutManager(layoutManager);
        recyclerView.setAdapter(tDeviceAdapter);

        connectBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                startMqttService();
                if (client != null && client.isConnected() && room_id != null) {
                    clientOperation.doPublish(UtilConstants.Topic.roomTouchPadFb(room_id), generateDeviceStatusMqttMsg(UtilConstants.DeviceStatus.ON));
                }
            }
        });

        videoBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent videoCallIntent = new Intent(TouchPadActivity.this, VideoChatViewActivity.class);
                Log.i(TAG, "onClick: start video chat");
                startActivity(videoCallIntent);
            }
        });

        findDeviceBtn.setEnabled(false);
        findDeviceBtn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                findDevice();
            }
        });
    }

    @Override
    public void initClient() throws MqttException {
        clientOperation = new ClientOperation(TouchPadActivity.this, broker);

        client = clientOperation.createClient();

        client.setCallback(new MqttCallback() {
            @Override
            public void connectionLost(Throwable cause) {
                Log.w(TAG, "connectionLost: start reconnection", cause);
                clientOperation.doConnect();
                registerAssignment();
                registerRoom();
            }

            @Override
            public void messageArrived(String topic, MqttMessage message) throws Exception {
                Log.i(TAG, "messageArrived: " + message.toString());
                if (topic.equals(UtilConstants.Topic.ASSIGN_ROOM)) {    // assign the device to a room
                    RoomBindInfo roomBindInfo = JSON.parseObject(message.toString(), RoomBindInfo.class);
                    if (registerInfo.getDevice_id().equals(roomBindInfo.getDevice_id())) {
                        room_id = roomBindInfo.getRoom_id();
                        registerRoom();
                        if (isTurnedOn) {
                            clientOperation.doPublish(UtilConstants.Topic.roomTouchPadFb(room_id), generateDeviceStatusMqttMsg(UtilConstants.DeviceStatus.ON));
                        } else {
                            clientOperation.doPublish(UtilConstants.Topic.roomTouchPadFb(room_id), generateDeviceStatusMqttMsg(UtilConstants.DeviceStatus.OFF));
                        }
                    }
                } else if (room_id != null) {

                    if (topic.equals(room_id)) {  // get controlled
                        ControlInfo controlInfo = JSON.parseObject(message.toString(), ControlInfo.class);
                        if (registerInfo.getDevice_id().equals(controlInfo.getDevice_id())) {
                            switch (controlInfo.getCommand().toLowerCase()) {
                                case UtilConstants.DeviceStatus.ON:
                                    turnOnDevice();
                                    break;
                                case UtilConstants.DeviceStatus.OFF:
                                    turnOffDevice();
                                    break;
                                default:
                                    break;
                            }
                        }
                    }

                    if (topic.equals(UtilConstants.Topic.roomFeedback(room_id))) {
                        DeviceStatusInfo deviceStatusInfo = JSON.parseObject(message.toString(), DeviceStatusInfo.class);

                        Log.i(TAG, "messageArrived: message converted to deviceStatusInfo: "
                                + "device id: " + deviceStatusInfo.getDevice_id() + "; device status: " +
                                deviceStatusInfo.getDevice_status());

                        updateListStatus(deviceStatusInfo);

                    }

                }
            }

            @Override
            public void deliveryComplete(IMqttDeliveryToken token) {
                Log.i(TAG, "Delivery complete callback: Publish Completed " + Arrays.toString(token.getTopics()));
            }
        });
    }

    @Override
    public void registerDevice() {
        MqttMessage message = new MqttMessage(device_info.getBytes());
        message.setQos(UtilConstants.Qos.QOS2);
        registerAssignment();
        clientOperation.doPublish(UtilConstants.Topic.REGISTER, message);
    }

    @Override
    public void registerRoom() {
        clientOperation.doSubscribe(room_id, UtilConstants.Qos.QOS2);
        clientOperation.doSubscribe(UtilConstants.Topic.roomFeedback(room_id), UtilConstants.Qos.QOS2);
    }

    @Override
    public void registerAssignment() {
        clientOperation.doSubscribe(UtilConstants.Topic.ASSIGN_ROOM, UtilConstants.Qos.QOS2);
    }

    @Override
    public void startMqttService() {

        // check new broker input.
        String curInput = brokerAddressEt.getText().toString();
        if (broker == null || !broker.equals(curInput)) {
            broker = curInput;
            client = null; //to start a new client
        }

        if (client == null) {
            try {
                initClient();
            } catch (MqttException e) {
                Log.e(TAG, "startMqttService: fail to create client", e);
//                e.printStackTrace();
            }
        }

        if (!client.isConnected()) {
            //reconnect for those initialized client instance but connection failed.
            clientOperation.doConnect();

            /**
             *  wait until connector callback invoke.
             */
            synchronized (clientOperation.waiter) {
                try {
                    Log.i(TAG, "wait in connection");

                    // wait for at most maxTTW
                    clientOperation.waiter.wait(clientOperation.maxTTW);
                } catch (InterruptedException e) {

                    Log.e(TAG, "timed out", e);

//                    e.printStackTrace();

                }
            }
            if (client.isConnected()) {

                Toast.makeText(TouchPadActivity.this, "Connected to " + broker,
                        Toast.LENGTH_SHORT).show();
                registerDevice();
            } else {

                Toast.makeText(TouchPadActivity.this, "Connect failed",
                        Toast.LENGTH_SHORT).show();
            }
        }
        findDeviceBtn.setEnabled(true);

    }

    private void turnOnDevice() {
        isTurnedOn = true;
        if (client != null && room_id != null) {
            clientOperation.doSubscribe(UtilConstants.Topic.roomFeedback(room_id), UtilConstants.Qos.QOS2);
            clientOperation.doPublish(UtilConstants.Topic.roomTouchPadFb(room_id), generateDeviceStatusMqttMsg(UtilConstants.DeviceStatus.ON));
        }

        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                findDeviceBtn.setEnabled(true);
            }
        });

//        new Thread(new Runnable() {
//            @Override
//            public void run() {
//                Message message = new Message();
//                message.what = SET_FIND_BTN_TRUE;
//                handler.sendMessage(message);
//            }
//        }).start();
    }

    private void turnOffDevice() {
        isTurnedOn = false;
        if (client != null && client.isConnected() && room_id != null) {
            clientOperation.doPublish(UtilConstants.Topic.roomTouchPadFb(room_id), generateDeviceStatusMqttMsg(UtilConstants.DeviceStatus.OFF));
            clientOperation.doUnsubscribe(UtilConstants.Topic.roomFeedback(room_id));
        }

        tDeviceList.clear();
        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                findDeviceBtn.setEnabled(false);
                tDeviceAdapter.notifyDataSetChanged();
            }
        });
//        new Thread(new Runnable() {
//            @Override
//            public void run() {
//                Message message = new Message();
//                message.what = SET_FIND_BTN_FALSE;
//                handler.sendMessage(message);
//            }
//        }).start();
//        tDeviceList.clear();
//        new Thread(new Runnable() {
//            @Override
//            public void run() {
//                Message message = new Message();
//                message.what = UPDATE_LIST;
//                handler.sendMessage(message);
//            }
//        }).start();
    }

    private void findDevice() {
        if (client != null && client.isConnected() && room_id != null) {
            clientOperation.doPublish(UtilConstants.Topic.roomTouchPad(room_id), new MqttMessage("find".getBytes()));
        }
    }

//    private static final int UPDATE_LIST = 1;
//    private static final int SET_FIND_BTN_FALSE = 2;
//    private static final int SET_FIND_BTN_TRUE = 3;
//    private Handler handler = new Handler() {
//        @Override
//        public void handleMessage(Message msg) {
//            switch (msg.what) {
//                case UPDATE_LIST:
//                    tDeviceAdapter.notifyDataSetChanged();
//                    break;
//                case SET_FIND_BTN_FALSE:
//                    findDeviceBtn.setEnabled(false);
//                    break;
//                case SET_FIND_BTN_TRUE:
//                    findDeviceBtn.setEnabled(true);
//                default:
//                    break;
//            }
//        }
//    };

    private void updateListStatus(DeviceStatusInfo deviceStatusInfo) {
        boolean isInList = false;
        String name = deviceStatusInfo.getDevice_name();
        String type = deviceStatusInfo.getDevice_type();
        String id = deviceStatusInfo.getDevice_id();
        String status = deviceStatusInfo.getDevice_status();
//        String roomId = deviceStatusInfo.getDevice_room_id();

        for (int i = 0; i < tDeviceList.size(); i++) {
            TMqttDevice device = tDeviceList.get(i);

            if (device.getDeviceId().equals(id)) {
                isInList = true;
                device.setStatus(status);
                Log.i(TAG, "updateListStatus: device status changed," +
                        " device id: " + device.getDeviceId() + "; device status: " +
                        device.getStatus());
                tDeviceList.set(i, device);
                break;
            }
        }

        if (!isInList) {
            int imageId;
            switch (type) {
                case UtilConstants.DeviceType
                        .AIRCON:
                    imageId = R.drawable.aircon_pic;
                    break;
                case UtilConstants.DeviceType
                        .LIGHT:
                    imageId = R.drawable.light_pic;
                    break;
                case UtilConstants.DeviceType
                        .SOUNDBOX:
                    imageId = R.drawable.sound_box_pic;
                    break;
                case UtilConstants.DeviceType
                        .TV:
                    imageId = R.drawable.tv_pic;
                    break;
                default:
                    imageId = R.drawable.light_pic;
                    break;
            }
            TMqttDevice device = new TMqttDevice(name, imageId, id, status, type/*, roomId*/);
            Log.i(TAG, "updateListStatus: add device to list: device id: " + device.getDeviceId()
                    + "; device status: " + device.getName() + "; device status: " + device.getStatus());
            tDeviceList.add(device);
        }
        Log.i(TAG, "updateListStatus: tDeviceList Changed, size: " + tDeviceList.size());

        runOnUiThread(new Runnable() {
            @Override
            public void run() {
                tDeviceAdapter.notifyDataSetChanged();
            }
        });
//        new Thread(new Runnable() {
//            @Override
//            public void run() {
//                Message message = new Message();
//                message.what = UPDATE_LIST;
//                handler.sendMessage(message);
//            }
//        }).start();

    }

    public void controlOtherDevice(ControlInfo controlInfo) {
        MqttMessage message = new MqttMessage(JSON.toJSONString(controlInfo).getBytes());
        clientOperation.doPublish(room_id, message);
    }

    private MqttMessage generateDeviceStatusMqttMsg(String status) {
        DeviceStatusInfo deviceStatusInfo = new DeviceStatusInfo();
        deviceStatusInfo.setDevice_id(registerInfo.getDevice_id());
        deviceStatusInfo.setDevice_status(status);
        deviceStatusInfo.setDevice_name(registerInfo.getDevice_name());
        deviceStatusInfo.setDevice_type(registerInfo.getDevice_type());
        String statusInfo = JSON.toJSONString(deviceStatusInfo);
        return new MqttMessage(statusInfo.getBytes());
    }

    @Override
    public void onDestroy() {

        if (isTurnedOn) {
            turnOffDevice();
        }
        if (client != null) {
            if (client.isConnected()) {
                try {
                    client.disconnect();
                    client.close();
                } catch (MqttException e) {
                    Log.e(TAG, "onDestroy: mqtt fail to disconnect", e);
                }
            }
        }

        super.onDestroy();
    }
}
